package com.linsen.core.web.aspect;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.linsen.core.common.aspect.BaseAspect;
import com.linsen.core.common.constant.LinsenConstant;
import com.linsen.core.common.context.TraceContextHolder;
import com.linsen.core.web.props.LinsenWebProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;

/**
 * @author zhaoyong
 * @date 2023/02/20
 */
@Aspect
@Slf4j
@Component
@RequiredArgsConstructor
public class RequestAccessLogAspect extends BaseAspect {
    private final LinsenWebProperties webProperties;

    @Pointcut("within(" + LinsenConstant.LINSEN_APP_PACKAGE + "..*) " + "&& (@annotation(org.springframework.web.bind.annotation.RequestMapping) " + "|| @annotation(org.springframework.web.bind.annotation.GetMapping) " + "|| @annotation(org.springframework.web.bind.annotation.PostMapping) " + "|| @annotation(org.springframework.web.bind.annotation.PutMapping) " + "|| @annotation(org.springframework.web.bind.annotation.DeleteMapping))")
    public void pointcut() {

    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        if (BooleanUtil.isFalse(this.webProperties.getRequestAccessLog())) {
            return point.proceed();
        }

        LocalDateTime startTime = LocalDateTime.now();
        String targetClassName = point.getTarget().getClass().getName();
        String targetMethodName = point.getSignature().getName();
        final String methodClass = StrUtil.format("{}.{}", targetClassName, targetMethodName);
        this.printRequestLog(methodClass, point.getArgs());
        Object result = point.proceed();
        this.printResponseLog(startTime, result);
        return result;
    }

//    @AfterThrowing(pointcut = "pointcut()", throwing = "e")
//    public void afterThrowing(JoinPoint point, Throwable e) {
//
//    }

    private void printRequestLog(final String methodClass, Object[] args) {
        HttpServletRequest request = super.getRequest();
        final String uri = request.getRequestURI();
        final String method = request.getMethod();
        final String ip = super.getClientIP(request);
        log.info("\n" + "\n========== Request =========" + "\nTrace Id: {}" + "\nMethod ClassName: {}" + "\nRequest Uri: {}" + "\nRequest Method: {}" + "\nRequest Args: {}" + "\nRequest Params: {}" + "\nRequest Headers: {}" + "\nRequest Ip: {}" + "\n========== Request =========" + "\n", TraceContextHolder.getTraceId(), methodClass, uri, method, JSON.toJSONString(new ArrayList<Object>() {{
            for (Object arg : args) {
                if (arg instanceof HttpServletRequest){
                    continue;
                }else if (arg instanceof HttpServletResponse){
                    continue;
                }else if (arg instanceof MultipartFile){
                    continue;
                }else if (arg instanceof MultipartFile[]){
                    continue;
                }else{
                    add(arg);
                }
            }
        }}), super.getRequestParams(request), super.getRequestHeaders(request), ip);
    }

    private void printResponseLog(final LocalDateTime startTime, Object result) {
        HttpServletResponse response = this.getResponse();
        if (log.isDebugEnabled()) {
            log.debug("\n" + "\n========== Response =========" + "\nTrace Id: {}" + "\nResponse Body: {}" + "\nResponse Headers: {}" + "\nTime: {}ms" + "\n========== Response =========" + "\n", TraceContextHolder.getTraceId(), JSON.toJSONString(result), super.getResponseHeaders(response), Duration.between(startTime, LocalDateTime.now()).toMillis());
        }
    }

    /**
     * 打印异常日志
     *
     * @param e
     */
    private void printExceptionLog(Throwable e) {
        if (this.webProperties.getPrintStackTrace()) {
            log.error("", e);
        } else {
            log.error("\n" + "\n========== Exception =========" + "\nTrace Id: {}" + "\nException ClassName: {}" + "\nException Message: {}" + "\n========== Exception =========" + "\n", TraceContextHolder.getTraceId(), e.getClass(), e.getMessage());
        }
    }
}
